package com.example.laundry.service;

import com.example.laundry.dto.*;
import com.example.laundry.entity.*;
import com.example.laundry.exception.OrderProcessingException;
import com.example.laundry.exception.ResourceNotFoundException;
import com.example.laundry.repository.LaundryOrderRepository;
import com.example.laundry.repository.LaundryItemRepository;
import com.example.laundry.repository.OrderStatusHistoryRepository;
import com.example.laundry.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class LaundryOrderServiceImpl implements LaundryOrderService {

    private final LaundryOrderRepository orderRepository;
    private final LaundryItemRepository itemRepository;
    private final OrderStatusHistoryRepository statusHistoryRepository;
    private final UserRepository userRepository;
    
    private static final Map<LaundryOrderEntity.OrderStatus, Set<LaundryOrderEntity.OrderStatus>> VALID_TRANSITIONS = 
        Map.of(
            LaundryOrderEntity.OrderStatus.PENDING, 
            EnumSet.of(LaundryOrderEntity.OrderStatus.CONFIRMED, LaundryOrderEntity.OrderStatus.CANCELLED),
            
            LaundryOrderEntity.OrderStatus.CONFIRMED, 
            EnumSet.of(LaundryOrderEntity.OrderStatus.IN_PROGRESS, LaundryOrderEntity.OrderStatus.CANCELLED),
            
            LaundryOrderEntity.OrderStatus.IN_PROGRESS, 
            EnumSet.of(LaundryOrderEntity.OrderStatus.READY_FOR_DELIVERY, LaundryOrderEntity.OrderStatus.CANCELLED),
            
            LaundryOrderEntity.OrderStatus.READY_FOR_DELIVERY, 
            EnumSet.of(LaundryOrderEntity.OrderStatus.COMPLETED, LaundryOrderEntity.OrderStatus.CANCELLED)
        );

    @Override
    @Transactional
    public LaundryOrderDto createOrder(LaundryOrderDto orderDto, Long userId) {
        UserEntity user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + userId));

        LaundryOrderEntity order = new LaundryOrderEntity();
        order.setUser(user);
        order.setOrderNumber(generateOrderNumber());
        order.setCustomerName(orderDto.getCustomerName());
        order.setCustomerPhone(orderDto.getCustomerPhone());
        order.setPickupAddress(orderDto.getPickupAddress());
        order.setDeliveryAddress(orderDto.getDeliveryAddress());
        order.setStatus(LaundryOrderEntity.OrderStatus.PENDING);
        order.setCreatedAt(LocalDateTime.now());

        // Calculate total amount from items
        BigDecimal totalAmount = orderDto.getItems().stream()
                .map(item -> item.getUnitPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        order.setTotalAmount(totalAmount);

        // Save order first to get ID
        LaundryOrderEntity savedOrder = orderRepository.save(order);

        // Save items
        List<LaundryItemEntity> items = orderDto.getItems().stream()
                .map(itemDto -> {
                    LaundryItemEntity item = new LaundryItemEntity();
                    item.setOrder(savedOrder);
                    item.setItemName(itemDto.getItemName());
                    item.setItemType(itemDto.getItemType());
                    item.setQuantity(itemDto.getQuantity());
                    item.setUnitPrice(itemDto.getUnitPrice());
                    item.setNotes(itemDto.getNotes());
                    return item;
                })
                .collect(Collectors.toList());
        itemRepository.saveAll(items);

        // Add initial status history
        addStatusHistory(savedOrder, null, savedOrder.getStatus().name(), user, "Order created");

        return LaundryOrderDto.fromEntity(savedOrder);
    }

    @Override
    @Transactional
    public LaundryOrderDto updateOrder(Long id, LaundryOrderDto orderDto) {
        LaundryOrderEntity order = orderRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Order not found with id: " + id));

        order.setCustomerName(orderDto.getCustomerName());
        order.setCustomerPhone(orderDto.getCustomerPhone());
        order.setPickupAddress(orderDto.getPickupAddress());
        order.setDeliveryAddress(orderDto.getDeliveryAddress());

        // Recalculate total amount if items changed
        if (orderDto.getItems() != null && !orderDto.getItems().isEmpty()) {
            BigDecimal totalAmount = orderDto.getItems().stream()
                    .map(item -> item.getUnitPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            order.setTotalAmount(totalAmount);
        }

        LaundryOrderEntity updatedOrder = orderRepository.save(order);
        return LaundryOrderDto.fromEntity(updatedOrder);
    }

    @Override
    @Transactional(readOnly = true)
    public LaundryOrderDto getOrderById(Long id) {
        LaundryOrderEntity order = orderRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Order not found with id: " + id));
        
        LaundryOrderDto dto = convertToDto(order);
        dto.setStatusHistory(order.getStatusHistory().stream()
                .map(OrderStatusHistoryDto::fromEntity)
                .collect(Collectors.toList()));
        
        return dto;
    }

    @Override
    @Transactional(readOnly = true)
    public Page<LaundryOrderDto> getAllOrders(Pageable pageable) {
        return orderRepository.findAll(pageable)
                .map(this::convertToDto);
    }

    @Override
    @Transactional(readOnly = true)
    public List<LaundryOrderDto> getOrdersByUser(Long userId) {
        UserEntity user = userRepository.findById(userId)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + userId));
        
        return orderRepository.findByUser(user).stream()
                .map(this::convertToDto)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional
    public void deleteOrder(Long id) {
        LaundryOrderEntity order = orderRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Order not found with id: " + id));
        
        orderRepository.delete(order);
    }

    @Override
    @Transactional
    public LaundryOrderDto updateOrderStatus(Long id, String newStatus, Long changedByUserId) {
        LaundryOrderEntity order = orderRepository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("Order not found with id: " + id));
        
        UserEntity changedBy = userRepository.findById(changedByUserId)
                .orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + changedByUserId));

        try {
            LaundryOrderEntity.OrderStatus status = LaundryOrderEntity.OrderStatus.valueOf(newStatus);
            
            // Validate status transition
            if (!isValidStatusTransition(order.getStatus(), status)) {
                throw new OrderProcessingException("Invalid status transition from " + 
                    order.getStatus() + " to " + status);
            }

            String oldStatus = order.getStatus().name();
            order.setStatus(status);
            LaundryOrderEntity updatedOrder = orderRepository.save(order);

            // Record status change
            addStatusHistory(order, oldStatus, newStatus, changedBy, "Status updated");

            return LaundryOrderDto.fromEntity(updatedOrder);
        } catch (IllegalArgumentException e) {
            throw new OrderProcessingException("Invalid status value: " + newStatus);
        }
    }

    @Override
    @Transactional(readOnly = true)
    public List<OrderStatusHistoryDto> getOrderStatusHistory(Long orderId) {
        LaundryOrderEntity order = orderRepository.findById(orderId)
                .orElseThrow(() -> new ResourceNotFoundException("Order not found with id: " + orderId));
        
        return statusHistoryRepository.findByOrderOrderByCreatedAtDesc(order).stream()
                .map(OrderStatusHistoryDto::fromEntity)
                .collect(Collectors.toList());
    }

    @Override
    @Transactional
    public LaundryOrderDto addItemsToOrder(Long orderId, List<LaundryItemDto> items) {
        LaundryOrderEntity order = orderRepository.findById(orderId)
                .orElseThrow(() -> new ResourceNotFoundException("Order not found with id: " + orderId));

        List<LaundryItemEntity> newItems = items.stream()
                .map(itemDto -> {
                    LaundryItemEntity item = new LaundryItemEntity();
                    item.setOrder(order);
                    item.setItemName(itemDto.getItemName());
                    item.setItemType(itemDto.getItemType());
                    item.setQuantity(itemDto.getQuantity());
                    item.setUnitPrice(itemDto.getUnitPrice());
                    item.setNotes(itemDto.getNotes());
                    return item;
                })
                .collect(Collectors.toList());

        itemRepository.saveAll(newItems);

        // Recalculate total amount
        BigDecimal totalAmount = order.getItems().stream()
                .map(item -> item.getUnitPrice().multiply(BigDecimal.valueOf(item.getQuantity())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        order.setTotalAmount(totalAmount);
        LaundryOrderEntity updatedOrder = orderRepository.save(order);

        return LaundryOrderDto.fromEntity(updatedOrder);
    }

    @Override
    @Transactional
    public LaundryOrderDto removeItemFromOrder(Long orderId, Long itemId) {
        LaundryOrderEntity order = orderRepository.findById(orderId)
                .orElseThrow(() -> new ResourceNotFoundException("Order not found with id: " + orderId));

        LaundryItemEntity item = itemRepository.findById(itemId)
                .orElseThrow(() -> new ResourceNotFoundException("Item not found with id: " + itemId));

        if (!item.getOrder().getId().equals(orderId)) {
            throw new IllegalArgumentException("Item does not belong to the specified order");
        }

        itemRepository.delete(item);

        // Recalculate total amount
        BigDecimal totalAmount = order.getItems().stream()
                .map(i -> i.getUnitPrice().multiply(BigDecimal.valueOf(i.getQuantity())))
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        order.setTotalAmount(totalAmount);
        LaundryOrderEntity updatedOrder = orderRepository.save(order);

        return LaundryOrderDto.fromEntity(updatedOrder);
    }

    private String generateOrderNumber() {
        return "ORD-" + UUID.randomUUID().toString().substring(0, 8).toUpperCase();
    }

    private void addStatusHistory(LaundryOrderEntity order, String fromStatus, String toStatus, 
                               UserEntity changedBy, String notes) {
        OrderStatusHistoryEntity history = new OrderStatusHistoryEntity();
        history.setOrder(order);
        history.setFromStatus(fromStatus);
        history.setToStatus(toStatus);
        history.setChangedBy(changedBy);
        history.setNotes(notes);
        statusHistoryRepository.save(history);
    }

    private boolean isValidStatusTransition(LaundryOrderEntity.OrderStatus currentStatus, 
                                         LaundryOrderEntity.OrderStatus newStatus) {
        // 已完成或已取消的订单不能再变更状态
        if (currentStatus == LaundryOrderEntity.OrderStatus.COMPLETED || 
            currentStatus == LaundryOrderEntity.OrderStatus.CANCELLED) {
            return false;
        }
        
        // 查找允许的转换集合
        Set<LaundryOrderEntity.OrderStatus> allowedStatuses = VALID_TRANSITIONS.getOrDefault(currentStatus, EnumSet.noneOf(LaundryOrderEntity.OrderStatus.class));
        
        return allowedStatuses.contains(newStatus);
    }
    
    private LaundryOrderDto convertToDto(LaundryOrderEntity order) {
        LaundryOrderDto dto = LaundryOrderDto.fromEntity(order);
        dto.setItems(order.getItems().stream()
                .map(LaundryItemDto::fromEntity)
                .collect(Collectors.toList()));
        return dto;
    }
}